﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#if !SILVERLIGHT
using System.Data.Common;
using System.Data;
#endif
using EffiProz.Core.Errors;
using EffiProz.Core.Lib;


namespace EffiProz.Core.DataTypes
{
    public class IntervalType : DTIType
    {
        public bool defaultPrecision;
        public bool isYearMonth;

        private IntervalType(int typeGroup, int type, long precision, int scale,
                             int startIntervalType, int endIntervalType,
                             bool defaultPrecision) :
            base(typeGroup, type, precision, scale, startIntervalType,
                 endIntervalType)
        {

            switch (startIntervalType)
            {

                case Types.SQL_INTERVAL_YEAR:
                case Types.SQL_INTERVAL_MONTH:
                    isYearMonth = true;
                    break;

                default:
                    isYearMonth = false;
                    break;
            }

            this.defaultPrecision = defaultPrecision;
        }

        public override int displaySize()
        {

            switch (typeCode)
            {

                case Types.SQL_INTERVAL_YEAR:
                    return (int)precision + 1;

                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                    return (int)precision + 4;

                case Types.SQL_INTERVAL_MONTH:
                    return (int)precision + 1;

                case Types.SQL_INTERVAL_DAY:
                    return (int)precision + 1;

                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                    return (int)precision + 4;

                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                    return (int)precision + 7;

                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                    return (int)precision + 10 + (scale == 0 ? 0
                                                              : scale + 1);

                case Types.SQL_INTERVAL_HOUR:
                    return (int)precision + 1;

                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                    return (int)precision + 4;

                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                    return (int)precision + 7 + (scale == 0 ? 0
                                                             : scale + 1);

                case Types.SQL_INTERVAL_MINUTE:
                    return (int)precision + 1;

                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                    return (int)precision + 4 + (scale == 0 ? 0
                                                             : scale + 1);

                case Types.SQL_INTERVAL_SECOND:
                    return (int)precision + 1 + (scale == 0 ? 0
                                                             : scale + 1);

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }
        }

        public override int getAdoTypeCode()
        {

            // no JDBC number is available
#if !SILVERLIGHT
            return (int)DbType.Object;
#else
            return -1;
#endif
        }

        public override  String getCSharpClassName()
        {

            switch (typeCode)
            {

                case Types.SQL_INTERVAL_YEAR:
                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                case Types.SQL_INTERVAL_MONTH:
                    return typeof(IntervalMonthData).FullName;

                case Types.SQL_INTERVAL_DAY:
                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                case Types.SQL_INTERVAL_HOUR:
                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                case Types.SQL_INTERVAL_MINUTE:
                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                case Types.SQL_INTERVAL_SECOND:
                    return typeof(IntervalSecondData).FullName;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }
        }

        public override int getAdoPrecision()
        {
            return this.displaySize();
        }

        public override int getSQLGenericTypeCode()
        {
            return Types.SQL_INTERVAL;
        }

        public override String getNameString()
        {
            return "INTERVAL " + getQualifier(typeCode);
        }

        public static String getQualifier(int type)
        {

            switch (type)
            {

                case Types.SQL_INTERVAL_YEAR:
                    return "YEAR";

                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                    return "YEAR TO MONTH";

                case Types.SQL_INTERVAL_MONTH:
                    return "MONTH";

                case Types.SQL_INTERVAL_DAY:
                    return "DAY";

                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                    return "DAY TO HOUR";

                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                    return "DAY TO MINUTE";

                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                    return "DAY TO SECOND";

                case Types.SQL_INTERVAL_HOUR:
                    return "HOUR";

                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                    return "HOUR TO MINUTE";

                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                    return "HOUR TO SECOND";

                case Types.SQL_INTERVAL_MINUTE:
                    return "MINUTE";

                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                    return "MINUTE TO SECOND";

                case Types.SQL_INTERVAL_SECOND:
                    return "SECOND";

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }
        }

        public override String getDefinition()
        {

            if (precision == defaultIntervalPrecision
                    && (endIntervalType != Types.SQL_INTERVAL_SECOND
                        || scale == defaultIntervalFractionPrecision))
            {
                return getNameString();
            }

            StringBuilder sb = new StringBuilder(32);

            sb.Append(Tokens.T_INTERVAL).Append(' ');
            sb.Append(getQualifier(startIntervalType));

            if (typeCode == Types.SQL_INTERVAL_SECOND)
            {
                sb.Append('(');
                sb.Append(precision);

                if (scale != defaultIntervalFractionPrecision)
                {
                    sb.Append(',');
                    sb.Append(scale);
                }

                sb.Append(')');

                return sb.ToString();
            }

            if (precision != defaultIntervalPrecision)
            {
                sb.Append('(');
                sb.Append(precision);
                sb.Append(')');
            }

            if (startIntervalType != endIntervalType)
            {
                sb.Append(' ');
                sb.Append(Tokens.T_TO);
                sb.Append(' ');
                sb.Append(Tokens.SQL_INTERVAL_FIELD_NAMES[endPartIndex]);

                if (endIntervalType == Types.SQL_INTERVAL_SECOND
                        && scale != defaultIntervalFractionPrecision)
                {
                    sb.Append('(');
                    sb.Append(scale);
                    sb.Append(')');
                }
            }

            return sb.ToString();
        }

        public override bool isIntervalType()
        {
            return true;
        }

        public bool isYearMonthIntervalType()
        {

            switch (typeCode)
            {

                case Types.SQL_INTERVAL_YEAR:
                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                case Types.SQL_INTERVAL_MONTH:
                    return true;

                default:
                    return false;
            }
        }

        public bool isDaySecondIntervalType()
        {

            switch (typeCode)
            {

                case Types.SQL_INTERVAL_DAY:
                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                case Types.SQL_INTERVAL_HOUR:
                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                case Types.SQL_INTERVAL_MINUTE:
                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                case Types.SQL_INTERVAL_SECOND:
                    return true;

                default:
                    return false;
            }
        }

        public override bool acceptsPrecision()
        {
            return true;
        }

        public override bool acceptsFractionalPrecision()
        {
            return endIntervalType == Types.SQL_INTERVAL_SECOND;
        }

        public override  SqlType getAggregateType(SqlType other)
        {

            if (typeCode == other.typeCode)
            {
                if (precision >= other.precision && scale >= other.scale)
                {
                    return this;
                }
                else if (precision <= other.precision && scale <= other.scale)
                {
                    return other;
                }
            }

            if (other == SQL_ALL_TYPES)
            {
                return this;
            }

            if (other.isCharacterType())
            {
                return other.getAggregateType(this);
            }

            if (!other.isIntervalType())
            {
                throw Error.error(ErrorCode.X_42562);
            }

            int startType = ((IntervalType)other).startIntervalType
                            > startIntervalType ? startIntervalType
                                                : ((IntervalType)other)
                                                    .startIntervalType;
            int endType = ((IntervalType)other).endIntervalType > endIntervalType
                          ? ((IntervalType)other).endIntervalType
                          : endIntervalType;
            int newType = getCombinedIntervalType(startType, endType);
            long newPrecision = precision > other.precision ? precision
                                                            : other.precision;
            int newScale = scale > other.scale ? scale
                                                    : other.scale;

            try
            {
                return getIntervalType(newType, startType, endType, newPrecision,
                                       newScale, false);
            }
            catch (Exception )
            {
                throw Error.error(ErrorCode.X_42562);
            }
        }

        public override SqlType getCombinedType(SqlType other, int operation)
        {

            switch (operation)
            {

                case OpTypes.MULTIPLY:
                    if (other.isNumberType())
                    {
                        return getIntervalType(this, maxIntervalPrecision, scale);
                    }
                    break;

                case OpTypes.DIVIDE:
                    if (other.isNumberType())
                    {
                        return this;
                    }
                    break;

                case OpTypes.ADD:
                    if (other.isDateTimeType())
                    {
                        return other.getCombinedType(this, operation);
                    }
                    else if (other.isIntervalType())
                    {
                        IntervalType newType =
                            (IntervalType)getAggregateType(other);

                        return getIntervalType(newType, maxIntervalPrecision, 0);
                    }
                    break;

                case OpTypes.SUBTRACT:
                default:
                    return getAggregateType(other);
            }

            throw Error.error(ErrorCode.X_42562);
        }

        public override int compare(Session session, Object a, Object b)
        {

            if (a == b)
            {
                return 0;
            }

            if (a == null)
            {
                return -1;
            }

            if (b == null)
            {
                return 1;
            }

            switch (typeCode)
            {

                case Types.SQL_INTERVAL_YEAR:
                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                case Types.SQL_INTERVAL_MONTH:
                    return ((IntervalMonthData)a).compareTo(
                        (IntervalMonthData)b);

                case Types.SQL_INTERVAL_DAY:
                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                case Types.SQL_INTERVAL_HOUR:
                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                case Types.SQL_INTERVAL_MINUTE:
                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                case Types.SQL_INTERVAL_SECOND:
                    return ((IntervalSecondData)a).compareTo(
                        (IntervalSecondData)b);

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }
        }

        public override Object convertToTypeLimits(SessionInterface session, Object a)
        {

            if (a == null)
            {
                return null;
            }

            if (a is IntervalMonthData)
            {
                IntervalMonthData im = (IntervalMonthData)a;

                if (im.units > getIntervalValueLimit())
                {
                    throw Error.error(ErrorCode.X_22015);
                }
            }
            else if (a is IntervalSecondData)
            {
                IntervalSecondData isec = (IntervalSecondData)a;

                if (isec.units > getIntervalValueLimit())
                {
                    throw Error.error(ErrorCode.X_22015);
                }

                //            int divisor = nanoScaleFactors[scale];
                //            is.nanos = (is.nanos / divisor) * divisor;
            }

            return a;
        }

        public override Object convertToType(SessionInterface session, Object a,
                                    SqlType otherType)
        {

            if (a == null)
            {
                return null;
            }

            switch (otherType.typeCode)
            {

                case Types.SQL_CLOB:
                    a = a.ToString();
                    goto case Types.VARCHAR_IGNORECASE;
                // fall through
                case Types.SQL_CHAR:
                case Types.SQL_VARCHAR:
                case Types.VARCHAR_IGNORECASE:
                    {
                        return session.getScanner().convertToDatetimeInterval(session,
                                (String)a, this);
                    }
                case Types.TINYINT:
                case Types.SQL_SMALLINT:
                case Types.SQL_INTEGER:
                case Types.SQL_BIGINT:
                case Types.SQL_REAL:
                case Types.SQL_FLOAT:
                case Types.SQL_DOUBLE:
                case Types.SQL_NUMERIC:
                case Types.SQL_DECIMAL:
                    {
                        if (a is decimal)
                        {
                            if (!NumberType.isInLongLimits((decimal)a))
                            {
                                throw Error.error(ErrorCode.X_22015);
                            }
                        }

                        long value = Convert.ToInt64(a);

                        switch (this.endIntervalType)
                        {

                            case Types.SQL_INTERVAL_YEAR:
                                return IntervalMonthData.newIntervalYear(value, this);

                            case Types.SQL_INTERVAL_MONTH:
                                return IntervalMonthData.newIntervalMonth(value, this);

                            case Types.SQL_INTERVAL_DAY:
                                return IntervalSecondData.newIntervalDay(value, this);

                            case Types.SQL_INTERVAL_HOUR:
                                return IntervalSecondData.newIntervalHour(value, this);

                            case Types.SQL_INTERVAL_MINUTE:
                                return IntervalSecondData.newIntervalMinute(value,
                                        this);

                            case Types.SQL_INTERVAL_SECOND:
                                {
                                    int nanos = 0;

                                    if (scale > 0 && a is Decimal)
                                    {
                                        nanos = (int)NumberType.scaledDecimal(
                                            a, DTIType.maxFractionPrecision);
                                    }

                                    return new IntervalSecondData(value, nanos, this);
                                }
                            default:
                                throw Error.error(ErrorCode.X_42561);
                        }
                    }
                case Types.SQL_INTERVAL_YEAR:
                    {
                        long months = (((IntervalMonthData)a).units / 12) * 12;

                        return new IntervalMonthData(months, this);
                    }
                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                case Types.SQL_INTERVAL_MONTH:
                    {
                        long months = ((IntervalMonthData)a).units;

                        return new IntervalMonthData(months, this);
                    }
                case Types.SQL_INTERVAL_DAY:
                    {
                        long seconds = ((IntervalSecondData)a).units;

                        seconds = (seconds / DTIType.yearToSecondFactors[2])
                                  * DTIType.yearToSecondFactors[2];

                        return new IntervalSecondData(seconds, 0, this);
                    }
                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                case Types.SQL_INTERVAL_HOUR:
                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                case Types.SQL_INTERVAL_MINUTE:
                    {
                        long seconds = ((IntervalSecondData)a).units;

                        seconds = (seconds / DTIType.yearToSecondFactors[endPartIndex])
                                  * DTIType.yearToSecondFactors[endPartIndex];

                        return new IntervalSecondData(seconds, 0, this);
                    }
                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                case Types.SQL_INTERVAL_SECOND:
                    {
                        long seconds = ((IntervalSecondData)a).units;
                        int nanos = ((IntervalSecondData)a).nanos;

                        if (scale == 0)
                        {
                            nanos = 0;
                        }
                        else
                        {
                            nanos = (nanos / (DTIType.nanoScaleFactors[scale]))
                                    * (DTIType.nanoScaleFactors[scale]);
                        }

                        return new IntervalSecondData(seconds, nanos, this);
                    }
                default:
                    throw Error.error(ErrorCode.X_42561);
            }
        }

        public override Object convertToDefaultType(SessionInterface session, Object a)
        {

            if (a == null)
            {
                return null;
            }

            if (a is String)
            {
                return convertToType(null, a, SqlType.SQL_VARCHAR);
            }
            else
            {
                throw Error.error(ErrorCode.X_42561);
            }
        }

        public override String convertToString(Object a)
        {

            if (a == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_INTERVAL_YEAR:
                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                case Types.SQL_INTERVAL_MONTH:
                    return intervalMonthToString(a);

                case Types.SQL_INTERVAL_DAY:
                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                case Types.SQL_INTERVAL_HOUR:
                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                case Types.SQL_INTERVAL_MINUTE:
                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                case Types.SQL_INTERVAL_SECOND:
                    return intervalSecondToString(a);

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }
        }

        public override String convertToSQLString(Object a)
        {

            if (a == null)
            {
                return Tokens.T_NULL;
            }

            StringBuilder sb = new StringBuilder(32);

            sb.Append(Tokens.T_INTERVAL);
            sb.Append(StringConverter.toQuotedString(convertToString(a), '\'',
                    false));
            sb.Append(Tokens.SQL_INTERVAL_FIELD_NAMES[startPartIndex]);
            sb.Append(' ');
            sb.Append(Tokens.T_TO);
            sb.Append(' ');
            sb.Append(Tokens.SQL_INTERVAL_FIELD_NAMES[endPartIndex]);

            return sb.ToString();
        }

        public override bool canConvertFrom(SqlType otherType)
        {

            if (otherType.typeCode == Types.SQL_ALL_TYPES)
            {
                return true;
            }

            if (otherType.isCharacterType())
            {
                return true;
            }

            if (otherType.isNumberType())
            {
                return true;
            }

            if (!otherType.isIntervalType())
            {
                return false;
            }

            return !(isYearMonthIntervalType()
                     ^ ((IntervalType)otherType).isYearMonthIntervalType());
        }

        public override Object absolute(Object a)
        {

            if (a == null)
            {
                return null;
            }

            if (a is IntervalMonthData)
            {
                if (((IntervalMonthData)a).units < 0)
                {
                    return negate(a);
                }
            }
            else
            {
                if (((IntervalSecondData)a).units < 0
                        || ((IntervalSecondData)a).nanos < 0)
                {
                    return negate(a);
                }
            }

            return a;
        }

        public override Object negate(Object a)
        {

            if (a == null)
            {
                return null;
            }

            if (a is IntervalMonthData)
            {
                long units = ((IntervalMonthData)a).units;

                return new IntervalMonthData(-units, this);
            }
            else
            {
                long units = ((IntervalSecondData)a).units;
                int nanos = ((IntervalSecondData)a).nanos;

                return new IntervalSecondData(-units, -nanos, this, true);
            }
        }

        public override Object add(Object a, Object b, SqlType otherType)
        {

            if (a == null || b == null)
            {
                return null;
            }

            switch (typeCode)
            {

                case Types.SQL_INTERVAL_YEAR:
                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                case Types.SQL_INTERVAL_MONTH:
                    long months = ((IntervalMonthData)a).units
                                  + ((IntervalMonthData)b).units;

                    return new IntervalMonthData(months, this);

                case Types.SQL_INTERVAL_DAY:
                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                case Types.SQL_INTERVAL_HOUR:
                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                case Types.SQL_INTERVAL_MINUTE:
                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                case Types.SQL_INTERVAL_SECOND:
                    long seconds = ((IntervalSecondData)a).units
                                   + ((IntervalSecondData)b).units;
                    long nanos = ((IntervalSecondData)a).nanos
                                 + ((IntervalSecondData)b).nanos;

                    return new IntervalSecondData(seconds, nanos, this, true);

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }
        }

        public override Object subtract(Object a, Object b, SqlType otherType)
        {
         
                if (a == null || b == null)
                {
                    return null;
                }

                switch (typeCode)
                {
                    case Types.SQL_INTERVAL_YEAR:
                    case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                    case Types.SQL_INTERVAL_MONTH:
                        if (a is IntervalMonthData
                                && b is IntervalMonthData)
                        {
                            long months = ((IntervalMonthData)a).units
                                          - ((IntervalMonthData)b).units;

                            return new IntervalMonthData(months, this);
                        }
                        else if (a is TimestampData
                                 && b is TimestampData)
                        {
                            bool isYear = typeCode == Types.SQL_INTERVAL_YEAR;
                            long months =
                                DateTimeType.subtractMonths((TimestampData)a,
                                                            (TimestampData)b, isYear);

                            return new IntervalMonthData(months, this);
                        }

                        throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
                    case Types.SQL_INTERVAL_DAY:
                    case Types.SQL_INTERVAL_DAY_TO_HOUR:
                    case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                    case Types.SQL_INTERVAL_DAY_TO_SECOND:
                    case Types.SQL_INTERVAL_HOUR:
                    case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                    case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                    case Types.SQL_INTERVAL_MINUTE:
                    case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                    case Types.SQL_INTERVAL_SECOND:
                        if (a is IntervalSecondData
                                && b is IntervalSecondData)
                        {
                            long seconds = ((IntervalSecondData)a).units
                                           - ((IntervalSecondData)b).units;
                            long nanos = ((IntervalSecondData)a).nanos
                                         - ((IntervalSecondData)b).nanos;

                            return new IntervalSecondData(seconds, nanos, this, true);
                        }
                        else if (a is TimeData && b is TimeData)
                        {
                            long seconds = ((TimeData)a).getSeconds()
                                           - ((TimeData)b).getSeconds();
                            long nanos = ((TimeData)a).getNanos()
                                         - ((TimeData)b).getNanos();

                            return new IntervalSecondData(seconds, nanos, this, true);
                        }
                        else if (a is TimestampData
                                 && b is TimestampData)
                        {
                            long seconds = (((TimestampData)a).getSeconds()
                                            - ((TimestampData)b).getSeconds());
                            long nanos = ((TimestampData)a).getNanos()
                                         - ((TimestampData)b).getNanos();

                            return new IntervalSecondData(seconds, nanos, this, true);
                        }
                        goto default;
                    // fall through
                    default:
                        throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
                }
            
        }

        public override Object multiply(Object a, Object b)
        {
            return multiplyOrDivide(a, b, false);
        }

        public override Object divide(Object a, Object b)
        {
            return multiplyOrDivide(a, b, true);
        }

        private Object multiplyOrDivide(Object a, Object b, bool divide)
        {

            if (a == null || b == null)
            {
                return null;
            }

            if (a is ValueType)
            {
                Object temp = a;

                a = b;
                b = temp;
            }

            if (divide && NumberType.isZero(b))
            {
                throw Error.error(ErrorCode.X_22012);
            }

            NumberType factorType = NumberType.getNumberType(Types.SQL_DECIMAL,
                40, maxFractionPrecision);
            Decimal factor = Convert.ToDecimal(factorType.convertToDefaultType(null,
                b));
            Decimal units;

            if (isYearMonth)
            {
                units = (decimal)(((IntervalMonthData)a).units);
            }
            else
            {
                long value =
                    ((IntervalSecondData)a).units * DTIType.nanoScaleFactors[0]
                    + ((IntervalSecondData)a).nanos;

                units = (decimal)(value);
            }

            Decimal result = divide
                                ? (Decimal)factorType.divide(units, factor)
                                : (Decimal)factorType.multiply(units, factor);

            if (!NumberType.isInLongLimits(result))
            {
                throw Error.error(ErrorCode.X_22015);
            }

            if (isYearMonth)
            {
                return new IntervalMonthData((long)result, this);
            }

            int nanos = (int)NumberType.scaledDecimal(result,
                DTIType.maxFractionPrecision);

            return new IntervalSecondData((long)result, nanos, this, true);
        }

        String intervalMonthToString(Object a)
        {

            StringBuilder sb = new StringBuilder(8);
            long months = ((IntervalMonthData)a).units;

            if (months < 0)
            {
                months = -months;

                sb.Append('-');
            }

            for (int i = startPartIndex; i <= endPartIndex; i++)
            {
                int factor = DTIType.yearToSecondFactors[i];
                long part = months / factor;

                if (i == startPartIndex)
                {
                    int zeros = (int)precision - getPrecisionExponent(part);
                    /*
                                    for (int j = 0; j < zeros; j++) {
                                        buffer.Append('0');
                                    }
                    */
                }
                else if (part < 10)
                {
                    sb.Append('0');
                }

                sb.Append(part);

                months %= factor;

                if (i < endPartIndex)
                {
                    sb.Append((char)DTIType.yearToSecondSeparators[i]);
                }
            }

            return sb.ToString();
        }

        public String intervalSecondToString(Object a)
        {

            long seconds = ((IntervalSecondData)a).units;
            int nanos = ((IntervalSecondData)a).nanos;

            return intervalSecondToString(seconds, nanos, false);
        }

        public override int precedenceDegree(SqlType other)
        {

            if (other.isIntervalType())
            {
                int otherIndex = ((IntervalType)other).endPartIndex;

                return otherIndex - endPartIndex;
            }

            return int.MinValue;
        }

        public override int getStartIntervalType()
        {
            return startIntervalType;
        }

        public override  int getEndIntervalType()
        {
            return endIntervalType;
        }

        public static IntervalType newIntervalType(int type, long precision,
                int fractionPrecision)
        {

            int startType = getStartIntervalType(type);
            int endType = getEndIntervalType(type);
            int group = startType > Types.SQL_INTERVAL_MONTH
                        ? Types.SQL_INTERVAL_SECOND
                        : Types.SQL_INTERVAL_MONTH;

            return new IntervalType(group, type, precision, fractionPrecision,
                                    startType, endType, false);
        }

        public static IntervalType getIntervalType(IntervalType type,
                long precision, int fractionalPrecision)
        {

            if (type.precision >= precision && type.scale >= fractionalPrecision)
            {
                return type;
            }

            return getIntervalType(type.typeCode, precision, fractionalPrecision);
        }

        public static IntervalType getIntervalType(int type, long precision,
                int fractionPrecision)
        {

            int startType = getStartIntervalType(type);
            int endType = getEndIntervalType(type);

            return getIntervalType(type, startType, endType, precision,
                                   fractionPrecision, false);
        }

        public static IntervalType getIntervalType(int startIndex, int endIndex,
                long precision, int fractionPrecision)
        {

            bool defaultPrecision = precision == -1;

            if (startIndex == -1 || endIndex == -1)
            {
                throw Error.error(ErrorCode.X_22006);
            }

            if (startIndex > endIndex)
            {
                throw Error.error(ErrorCode.X_22006);
            }

            if (startIndex <= DTIType.INTERVAL_MONTH_INDEX
                    && endIndex > DTIType.INTERVAL_MONTH_INDEX)
            {
                throw Error.error(ErrorCode.X_22006);
            }

            int startType = DTIType.intervalParts[startIndex];
            int endType = DTIType.intervalParts[endIndex];
            int type = DTIType.intervalTypes[startIndex][endIndex];

            if (precision == 0 || precision > DTIType.maxIntervalPrecision
                    || fractionPrecision > DTIType.maxFractionPrecision)
            {
                throw Error.error(ErrorCode.X_42592);
            }

            if (precision == -1)
            {
                precision = DTIType.defaultIntervalPrecision;
            }

            if (fractionPrecision == -1)
            {
                fractionPrecision = endType == Types.SQL_INTERVAL_SECOND
                                    ? DTIType.defaultIntervalFractionPrecision
                                    : 0;
            }

            return getIntervalType(type, startType, endType, precision,
                                   fractionPrecision, defaultPrecision);
        }

        public static IntervalType getIntervalType(int type, int startType,
                int endType, long precision, int fractionPrecision,
                bool defaultPrecision)
        {

            int group = startType > Types.SQL_INTERVAL_MONTH
                        ? Types.SQL_INTERVAL_SECOND
                        : Types.SQL_INTERVAL_MONTH;

            if (defaultPrecision)
            {
                return new IntervalType(group, type, precision, fractionPrecision,
                                        startType, endType, defaultPrecision);
            }

            switch (type)
            {

                case Types.SQL_INTERVAL_YEAR:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_YEAR;
                    }
                    break;

                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_YEAR_TO_MONTH;
                    }
                    break;

                case Types.SQL_INTERVAL_MONTH:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_MONTH;
                    }
                    break;

                case Types.SQL_INTERVAL_DAY:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_DAY;
                    }
                    break;

                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_DAY_TO_HOUR;
                    }
                    break;

                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_DAY_TO_MINUTE;
                    }
                    break;

                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                    if (precision == DTIType.defaultIntervalPrecision
                            && fractionPrecision
                               == DTIType.defaultIntervalFractionPrecision)
                    {
                        return SQL_INTERVAL_DAY_TO_SECOND;
                    }
                    break;

                case Types.SQL_INTERVAL_HOUR:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_HOUR;
                    }
                    break;

                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_HOUR_TO_MINUTE;
                    }
                    break;

                case Types.SQL_INTERVAL_MINUTE:
                    if (precision == DTIType.defaultIntervalPrecision)
                    {
                        return SQL_INTERVAL_MINUTE;
                    }
                    break;

                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                    if (precision == DTIType.defaultIntervalPrecision
                            && fractionPrecision
                               == DTIType.defaultIntervalFractionPrecision)
                    {
                        return SQL_INTERVAL_HOUR_TO_SECOND;
                    }
                    break;

                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                    if (precision == DTIType.defaultIntervalPrecision
                            && fractionPrecision
                               == DTIType.defaultIntervalFractionPrecision)
                    {
                        return SQL_INTERVAL_MINUTE_TO_SECOND;
                    }
                    break;

                case Types.SQL_INTERVAL_SECOND:
                    if (precision == DTIType.defaultIntervalPrecision
                            && fractionPrecision
                               == DTIType.defaultIntervalFractionPrecision)
                    {
                        return SQL_INTERVAL_SECOND;
                    }
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }

            return new IntervalType(group, type, precision, fractionPrecision,
                                    startType, endType, defaultPrecision);
        }

        public static int getStartIntervalType(int type)
        {

            int startType;

            switch (type)
            {

                case Types.SQL_INTERVAL_YEAR:
                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                    startType = Types.SQL_INTERVAL_YEAR;
                    break;

                case Types.SQL_INTERVAL_MONTH:
                    startType = Types.SQL_INTERVAL_MONTH;
                    break;

                case Types.SQL_INTERVAL_DAY:
                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                    startType = Types.SQL_INTERVAL_DAY;
                    break;

                case Types.SQL_INTERVAL_HOUR:
                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                    startType = Types.SQL_INTERVAL_HOUR;
                    break;

                case Types.SQL_INTERVAL_MINUTE:
                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                    startType = Types.SQL_INTERVAL_MINUTE;
                    break;

                case Types.SQL_INTERVAL_SECOND:
                    startType = Types.SQL_INTERVAL_SECOND;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }

            return startType;
        }

        public static int getEndIntervalType(int type)
        {

            int endType;

            switch (type)
            {

                case Types.SQL_INTERVAL_YEAR:
                    endType = Types.SQL_INTERVAL_YEAR;
                    break;

                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                    endType = Types.SQL_INTERVAL_MONTH;
                    break;

                case Types.SQL_INTERVAL_MONTH:
                    endType = Types.SQL_INTERVAL_MONTH;
                    break;

                case Types.SQL_INTERVAL_DAY:
                    endType = Types.SQL_INTERVAL_DAY;
                    break;

                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                    endType = Types.SQL_INTERVAL_HOUR;
                    break;

                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                    endType = Types.SQL_INTERVAL_MINUTE;
                    break;

                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                    endType = Types.SQL_INTERVAL_SECOND;
                    break;

                case Types.SQL_INTERVAL_HOUR:
                    endType = Types.SQL_INTERVAL_HOUR;
                    break;

                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                    endType = Types.SQL_INTERVAL_MINUTE;
                    break;

                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                    endType = Types.SQL_INTERVAL_SECOND;
                    break;

                case Types.SQL_INTERVAL_MINUTE:
                    endType = Types.SQL_INTERVAL_MINUTE;
                    break;

                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                    endType = Types.SQL_INTERVAL_SECOND;
                    break;

                case Types.SQL_INTERVAL_SECOND:
                    endType = Types.SQL_INTERVAL_SECOND;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }

            return endType;
        }

        public static SqlType getCombinedIntervalType(IntervalType type1,
                IntervalType type2)
        {

            int startType = type2.startIntervalType > type1.startIntervalType
                            ? type1.startIntervalType
                            : type2.startIntervalType;
            int endType = type2.endIntervalType > type1.endIntervalType
                          ? type2.endIntervalType
                          : type1.endIntervalType;
            int type = getCombinedIntervalType(startType, endType);
            long precision = type1.precision > type2.precision ? type1.precision
                                                               : type2.precision;
            int fractionPrecision = type1.scale > type2.scale ? type1.scale
                                                               : type2.scale;

            return getIntervalType(type, startType, endType, precision,
                                   fractionPrecision, false);
        }

        public static int getCombinedIntervalType(int startType, int endType)
        {

            if (startType == endType)
            {
                return startType;
            }

            switch (startType)
            {

                case Types.SQL_INTERVAL_YEAR:
                    if (endType == Types.SQL_INTERVAL_MONTH)
                    {
                        return Types.SQL_INTERVAL_YEAR_TO_MONTH;
                    }
                    break;

                case Types.SQL_INTERVAL_DAY:
                    switch (endType)
                    {

                        case Types.SQL_INTERVAL_HOUR:
                            return Types.SQL_INTERVAL_DAY_TO_HOUR;

                        case Types.SQL_INTERVAL_MINUTE:
                            return Types.SQL_INTERVAL_DAY_TO_MINUTE;

                        case Types.SQL_INTERVAL_SECOND:
                            return Types.SQL_INTERVAL_DAY_TO_SECOND;
                    }
                    break;

                case Types.SQL_INTERVAL_HOUR:
                    switch (endType)
                    {

                        case Types.SQL_INTERVAL_MINUTE:
                            return Types.SQL_INTERVAL_HOUR_TO_MINUTE;

                        case Types.SQL_INTERVAL_SECOND:
                            return Types.SQL_INTERVAL_HOUR_TO_SECOND;
                    }
                    break;

                case Types.SQL_INTERVAL_MINUTE:
                    if (endType == Types.SQL_INTERVAL_SECOND)
                    {
                        return Types.SQL_INTERVAL_MINUTE_TO_SECOND;
                    }
                    break;

            }

            throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
        }

        public long getIntervalValueLimit()
        {

            long limit;

            switch (typeCode)
            {

                case Types.SQL_INTERVAL_YEAR:
                    limit = DTIType.precisionLimits[(int)precision] * 12;
                    break;

                case Types.SQL_INTERVAL_YEAR_TO_MONTH:
                    limit = DTIType.precisionLimits[(int)precision] * 12;
                    limit += 12;
                    break;

                case Types.SQL_INTERVAL_MONTH:
                    limit = DTIType.precisionLimits[(int)precision];
                    break;

                case Types.SQL_INTERVAL_DAY:
                    limit = DTIType.precisionLimits[(int)precision] * 24 * 60
                            * 60;
                    break;

                case Types.SQL_INTERVAL_DAY_TO_HOUR:
                    limit = DTIType.precisionLimits[(int)precision] * 24 * 60
                            * 60;
                    break;

                case Types.SQL_INTERVAL_DAY_TO_MINUTE:
                    limit = DTIType.precisionLimits[(int)precision] * 24 * 60
                            * 60;
                    break;

                case Types.SQL_INTERVAL_DAY_TO_SECOND:
                    limit = DTIType.precisionLimits[(int)precision] * 24 * 60
                            * 60;
                    break;

                case Types.SQL_INTERVAL_HOUR:
                    limit = DTIType.precisionLimits[(int)precision] * 60 * 60;
                    break;

                case Types.SQL_INTERVAL_HOUR_TO_MINUTE:
                    limit = DTIType.precisionLimits[(int)precision] * 60 * 60;
                    break;

                case Types.SQL_INTERVAL_HOUR_TO_SECOND:
                    limit = DTIType.precisionLimits[(int)precision] * 60 * 60;
                    break;

                case Types.SQL_INTERVAL_MINUTE:
                    limit = DTIType.precisionLimits[(int)precision] * 60;
                    break;

                case Types.SQL_INTERVAL_MINUTE_TO_SECOND:
                    limit = DTIType.precisionLimits[(int)precision] * 60;
                    break;

                case Types.SQL_INTERVAL_SECOND:
                    limit = DTIType.precisionLimits[(int)precision];
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }

            return limit;
        }

        public override  int getPart(Session session, Object interval, int part)
        {

            long units;

            switch (part)
            {

                case Types.SQL_INTERVAL_YEAR:
                    return (int)(((IntervalMonthData)interval).units / 12);

                case Types.SQL_INTERVAL_MONTH:
                    units = ((IntervalMonthData)interval).units;

                    return part == startIntervalType ? (int)units
                                                     : (int)(units % 12);

                case Types.SQL_INTERVAL_DAY:
                    return (int)(((IntervalSecondData)interval).units
                                  / (24 * 60 * 60));

                case Types.SQL_INTERVAL_HOUR:
                    {
                        units = ((IntervalSecondData)interval).units / (60 * 60);

                        return part == startIntervalType ? (int)units
                                                         : (int)(units % 24);
                    }
                case Types.SQL_INTERVAL_MINUTE:
                    {
                        units = ((IntervalSecondData)interval).units / 60;

                        return part == startIntervalType ? (int)units
                                                         : (int)(units % 60);
                    }
                case Types.SQL_INTERVAL_SECOND:
                    {
                        units = ((IntervalSecondData)interval).units;

                        return part == startIntervalType ? (int)units
                                                         : (int)(units % 60);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }
        }

        public long getSeconds(Object interval)
        {
            return ((IntervalSecondData)interval).units;
        }

        public override  Decimal getSecondPart(Object interval)
        {

            long seconds = ((IntervalSecondData)interval).units;

            if (typeCode != Types.SQL_INTERVAL_SECOND)
            {
                seconds %= 60;
            }

            int nanos = ((IntervalSecondData)interval).nanos;

            return getSecondPart(seconds, nanos);
        }

        public long convertToLong(Object interval)
        {

            switch (endIntervalType)
            {

                case Types.SQL_INTERVAL_YEAR:
                case Types.SQL_INTERVAL_MONTH:
                    long months = ((IntervalMonthData)interval).units;

                    return (months / DTIType.yearToSecondFactors[endPartIndex]);

                case Types.SQL_INTERVAL_DAY:
                case Types.SQL_INTERVAL_HOUR:
                case Types.SQL_INTERVAL_MINUTE:
                case Types.SQL_INTERVAL_SECOND:
                    {
                        long seconds = ((IntervalSecondData)interval).units;

                        return (seconds / DTIType.yearToSecondFactors[endPartIndex]);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "IntervalType");
            }
        }
    }
}
